package com.tbynet.shiro.core;

import cn.dreampie.common.Plugin;
import cn.dreampie.route.config.ResourceLoader;
import cn.dreampie.route.core.Resource;
import cn.dreampie.route.core.annotation.API;
import cn.dreampie.route.core.annotation.DELETE;
import cn.dreampie.route.core.annotation.GET;
import cn.dreampie.route.core.annotation.PATCH;
import cn.dreampie.route.core.annotation.POST;
import cn.dreampie.route.core.annotation.PUT;
import cn.dreampie.shiro.core.ClearShiro;
import cn.dreampie.shiro.core.JdbcAuthzService;
import cn.dreampie.shiro.core.ShiroKit;
import cn.dreampie.shiro.core.handler.*;
import org.apache.shiro.authz.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Shiro插件，启动时加载所有Shiro访问控制注解。
 * 
 * @author dafei
 */
@SuppressWarnings("unchecked")
public class ShiroPlugin implements Plugin {
	private static final Logger log = LoggerFactory.getLogger(ShiroPlugin.class);

	private static final String SLASH = "/";

	/**
	 * 匹配到多个url时false 表示匹配到多个 就必须多个权限 true表示 只需要第一个匹配到的权限匹配 如/a/b 可以匹配到/a/**
	 * 和/a/b** 两个权限 匹配到一个就算通过或者两个权限都需要才通过？
	 */
	private boolean isAnd = false;
	/**
	 * Shiro的几种访问控制注解
	 */
	private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] {
			RequiresPermissions.class, RequiresRoles.class, RequiresUser.class,
			RequiresGuest.class, RequiresAuthentication.class };

	/**
	 * 路由设定
	 */
	private final String resourcePackage;

	/**
	 * jdbc的权限加载器
	 */
	private JdbcAuthzService jdbcAuthzService;

	/**
	 * 构造函数
	 * 
	 * @param resourceLoader
	 *            路由设定
	 */
	public ShiroPlugin(String resourcePackage) {
		this.resourcePackage = resourcePackage;
	}

	public ShiroPlugin(String resourcePackage,
			JdbcAuthzService jdbcAuthzService) {
		this.resourcePackage = resourcePackage;
		this.jdbcAuthzService = jdbcAuthzService;
	}

	public ShiroPlugin(String resourcePackage,
			JdbcAuthzService jdbcAuthzService, boolean isAnd) {
		this.resourcePackage = resourcePackage;
		this.jdbcAuthzService = jdbcAuthzService;
		this.isAnd = isAnd;
	}

	/**
	 * 停止插件
	 */
	@Override
	public boolean stop() {
		return true;
	}

	/**
	 * 启动插件
	 */
	@Override
	public boolean start() {
		ResourceLoader resourceLoader = new ResourceLoader();
		resourceLoader.addIncludePackages(resourcePackage);
		resourceLoader.build();
		
		Set<String> excludedMethodName = buildExcludedMethodName();
		ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>();
		// 逐个访问所有Resource，解析Resource及method上的所有Shiro注解。
		// 并依据这些注解，actionKey提前构建好权限检查处理器。
		for (Class<? extends Resource> clazz : resourceLoader.getResources()) {

			// 获取Resource的所有Shiro注解。
			List<Annotation> controllerAnnotations = getAuthzAnnotations(clazz);
			// 逐个遍历方法。
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {

				// 排除掉Resource基类的所有方法，并且只关注有請求動作注解的method。
				if (!excludedMethodName.contains(method.getName()) && hasAnnotation(method)) {
//				if (!excludedMethodName.contains(method.getName()) && method.getParameterTypes().length == 0) {
					// 若该方法上存在ClearShiro注解，则对该method不进行访问控制检查。
					if (isClearShiroAnnotationPresent(method)) {
						continue;
					}
					// 获取方法的所有Shiro注解。
					List<Annotation> methodAnnotations = getAuthzAnnotations(method);
					// 依据Controller的注解和方法的注解来生成访问控制处理器。
					AuthzHandler authzHandler = createAuthzHandler(controllerAnnotations, methodAnnotations);
					// 生成访问控制处理器成功。
					if (authzHandler != null) {
						// 构建ActionKey，参考ActionMapping中实现
						String actionKey = createActionKey(clazz, method);
						// 添加映射
						authzMaps.put(actionKey, authzHandler);
						log.info("Shiro Plugin - action key:" + actionKey + ", authz handler:" + authzHandler);
					}
				}
			}
		}
		// 注入到ShiroKit类中。ShiroKit类以单例模式运行。
		ShiroKit.init(jdbcAuthzService, authzMaps, isAnd);
		return true;
	}

	private boolean hasAnnotation(Method method) {
//		boolean isFile = null == method.getAnnotation(FILE.class);
		boolean isDelete = null == method.getAnnotation(DELETE.class);
		boolean isGet = null == method.getAnnotation(GET.class);
		boolean isPatch = null == method.getAnnotation(PATCH.class);
		boolean isPost = null == method.getAnnotation(POST.class);
		boolean isPut = null == method.getAnnotation(PUT.class);

		if (isDelete && isGet && isPatch && isPost && isPut) {
			return false;
		} else {
			return true;
		}
	}

	/**
	 * 从Resource方法中构建出需要排除的方法列表
	 * 
	 * @return Set<String>
	 */
	private Set<String> buildExcludedMethodName() {
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Resource.class.getMethods();
		for (Method m : methods) {
			if (!hasAnnotation(m))
				// if (m.getParameterTypes().length == 0)
				excludedMethodName.add(m.getName());
		}
		return excludedMethodName;
	}

	/**
	 * 依据Resource的注解和方法的注解来生成访问控制处理器。
	 * 
	 * @param controllerAnnotations
	 *            Resource的注解
	 * @param methodAnnotations
	 *            方法的注解
	 * @return 访问控制处理器
	 */
	private AuthzHandler createAuthzHandler(
			List<Annotation> controllerAnnotations,
			List<Annotation> methodAnnotations) {

		// 没有注解
		if (controllerAnnotations.size() == 0 && methodAnnotations.size() == 0) {
			return null;
		}
		// 至少有一个注解
		List<AuthzHandler> authzHandlers = new ArrayList<AuthzHandler>(5);
		for (int index = 0; index < 5; index++) {
			authzHandlers.add(null);
		}

		// 逐个扫描注解，若是相应的注解则在相应的位置赋值。
		scanAnnotation(authzHandlers, controllerAnnotations);
		// 逐个扫描注解，若是相应的注解则在相应的位置赋值。函数的注解优先级高于Resource
		scanAnnotation(authzHandlers, methodAnnotations);

		// 去除空值
		List<AuthzHandler> finalAuthzHandlers = new ArrayList<AuthzHandler>();
		for (AuthzHandler a : authzHandlers) {
			if (a != null) {
				finalAuthzHandlers.add(a);
			}
		}
		authzHandlers = null;
		// 存在多个，则构建组合AuthzHandler
		if (finalAuthzHandlers.size() > 1) {
			return new CompositeAuthzHandler(finalAuthzHandlers);
		}
		// 一个的话直接返回
		return finalAuthzHandlers.get(0);
	}

	/**
	 * 逐个扫描注解，若是相应的注解则在相应的位置赋值。 注解的处理是有顺序的，依次为RequiresRoles，RequiresPermissions，
	 * RequiresAuthentication，RequiresUser，RequiresGuest
	 * 
	 * @param authzArray
	 * @param annotations
	 */
	private void scanAnnotation(List<AuthzHandler> authzArray,
			List<Annotation> annotations) {
		if (null == annotations || 0 == annotations.size()) {
			return;
		}
		for (Annotation a : annotations) {
			if (a instanceof RequiresRoles) {
				authzArray.set(0, new RoleAuthzHandler(a));
			} else if (a instanceof RequiresPermissions) {
				authzArray.set(1, new PermissionAuthzHandler(a));
			} else if (a instanceof RequiresAuthentication) {
				authzArray.set(2, AuthenticatedAuthzHandler.me());
			} else if (a instanceof RequiresUser) {
				authzArray.set(3, UserAuthzHandler.me());
			} else if (a instanceof RequiresGuest) {
				authzArray.set(4, GuestAuthzHandler.me());
			}
		}
	}
	
	/**
	 * 获取api部分
	 * 
	 * @param resourceClazz
	 *            resource class
	 * @return url apiPath
	 */
	private String getApi(Class<? extends Resource> resourceClazz) {
		API api;
		String apiPath = "";
		api = resourceClazz.getAnnotation(API.class);
		if (api != null) {
			apiPath = api.value();
			if (!apiPath.equals("")) {
				if (!apiPath.startsWith("/")) {
					apiPath = "/" + apiPath;
				}
			}
		}
		Class<?> superClazz = resourceClazz.getSuperclass();
		if (Resource.class.isAssignableFrom(superClazz)) {
			apiPath = getApi((Class<? extends Resource>) superClazz) + apiPath;
		}
		return apiPath;
	}

	private String getApi(String apiPath, String methodPath) {
		if (!methodPath.equals("")) {
			if (!methodPath.startsWith(SLASH)) {
				apiPath = apiPath + SLASH + methodPath;
			} else {
				apiPath = apiPath + methodPath;
			}
		}
		return apiPath;
	}
	
	private String createActionKey(Class<? extends Resource> controllerClass, Method method) {
		String apiPath = getApi(controllerClass);
		String actionKey = "";
		
	    DELETE delete = null;
	    GET get = null;
	    POST post = null;
	    PUT put = null;
	    PATCH patch = null;

	    delete = method.getAnnotation(DELETE.class);
	    if(null != delete) {
	    	actionKey = getApi(apiPath, delete.value());
	    }
	    
	    get = method.getAnnotation(GET.class);
	    if(null != get) {
	    	actionKey = getApi(apiPath, get.value());
	    }
	    
	    post = method.getAnnotation(POST.class);
	    if(null != post) {
	    	actionKey = getApi(apiPath, post.value());
	    }
	    
	    put = method.getAnnotation(PUT.class);
	    if(null != put) {
	    	actionKey = getApi(apiPath, put.value());
	    }
	    
	    patch = method.getAnnotation(PATCH.class);
	    if(null != patch) {
	    	actionKey = getApi(apiPath, patch.value());
	    }
		
		return actionKey;
	}

//	/**
//	 * 构建actionkey，参考ActionMapping中的实现。
//	 * 
//	 * @param controllerClass
//	 * @param method
//	 * @param controllerKey
//	 * @return
//	 */
//	private String createActionKey(Class<? extends Resource> controllerClass, Method method, String controllerKey) {
//		String methodName = method.getName();
//		String actionKey = "";
//
//		ActionKey ak = method.getAnnotation(ActionKey.class);
//		if (ak != null) {
//			actionKey = ak.value().trim();
//			if ("".equals(actionKey))
//				throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");
//			if (!actionKey.startsWith(SLASH))
//				actionKey = SLASH + actionKey;
//		} else if (methodName.equals("index")) {
//			actionKey = controllerKey;
//		} else {
//			actionKey = controllerKey.equals(SLASH) ? SLASH + methodName
//					: controllerKey + SLASH + methodName;
//		}
//		return actionKey;
//	}

	/**
	 * 返回该方法的所有访问控制注解
	 * 
	 * @param method
	 * @return List<Annotation>
	 */
	private List<Annotation> getAuthzAnnotations(Method method) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = method.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}

	/**
	 * 返回该Resource的所有访问控制注解
	 * 
	 * @return List<Annotation>
	 */
	private List<Annotation> getAuthzAnnotations(
			Class<? extends Resource> targetClass) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = targetClass.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}

	/**
	 * 该方法上是否有ClearShiro注解
	 * 
	 * @param method
	 * @return boolean
	 */
	private boolean isClearShiroAnnotationPresent(Method method) {
		Annotation a = method.getAnnotation(ClearShiro.class);
		if (a != null) {
			return true;
		}
		return false;
	}
}
